/**
 * File name: MethodFinder.java Author: Willard Wang Create date: Jun 26, 2013
 */
package edu.sjtu.se.prophet.tools;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.LineMap;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreeScanner;
import com.sun.source.util.Trees;

/**
 * @author deathspeeder
 *         http://stackoverflow.com/questions/1928157/how-to-get-surrounding
 *         -method-in-java-source-file-for-a-given-line-number
 * 
 */
public class MethodFinder {
    static List<String> methodList = new ArrayList<String>();
    static List<Long> startNumberList = new ArrayList<Long>();
    static List<Long> endNumberList = new ArrayList<Long>();

    /**
     * @param args
     */
    public static void main(String[] args) {
        if (args.length <= 1) {
            System.err
                    .println("Please specify the source code file and line numbers.");
            System.exit(-1);
        }

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> diagnosticsCollector = new DiagnosticCollector<JavaFileObject>();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(
                diagnosticsCollector, null, null);
        Iterable<? extends JavaFileObject> fileObjects = fileManager
                .getJavaFileObjects(args[0]);
        CompilationTask task = compiler.getTask(null, fileManager,
                diagnosticsCollector, null, null, fileObjects);

        // Here we switch to Sun-specific APIs
        JavacTask javacTask = (JavacTask) task;
        SourcePositions sourcePositions = Trees.instance(javacTask)
                .getSourcePositions();
        Iterable<? extends CompilationUnitTree> parseResult = null;
        try {
            parseResult = javacTask.parse();
        } catch (IOException e) {

            // Parsing failed
            e.printStackTrace();
            System.exit(0);
        }
        for (CompilationUnitTree compilationUnitTree : parseResult) {
            compilationUnitTree.accept(new MethodLineLogger(
                    compilationUnitTree, sourcePositions), null);
        }

        String[] lines = args[1].split(",");
        long start = Long.parseLong(lines[0]);
        long end = start + Long.parseLong(lines[1]);

        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < startNumberList.size(); i++) {
            if ((startNumberList.get(i) <= start && start <= endNumberList
                    .get(i))
                    || (startNumberList.get(i) >= start && end >= endNumberList
                            .get(i))
                    || (startNumberList.get(i) <= end && end <= endNumberList
                            .get(i)))
                builder.append(methodList.get(i) + ";");
        }
        if (builder.length() > 0)
            builder.deleteCharAt(builder.length() - 1);
        System.out.println(builder.toString());
    }

    private static class MethodLineLogger extends TreeScanner<Void, Void> {
        private final CompilationUnitTree compilationUnitTree;
        private final SourcePositions sourcePositions;
        private final LineMap lineMap;

        private MethodLineLogger(CompilationUnitTree compilationUnitTree,
                SourcePositions sourcePositions) {
            this.compilationUnitTree = compilationUnitTree;
            this.sourcePositions = sourcePositions;
            this.lineMap = compilationUnitTree.getLineMap();
        }

        @Override
        public Void visitMethod(MethodTree arg0, Void arg1) {
            long startPosition = sourcePositions.getStartPosition(
                    compilationUnitTree, arg0);
            long startLine = lineMap.getLineNumber(startPosition);
            long endPosition = sourcePositions.getEndPosition(
                    compilationUnitTree, arg0);
            long endLine = lineMap.getLineNumber(endPosition);

            StringBuilder builder = new StringBuilder();
            for (VariableTree t : arg0.getParameters()) {
                builder.append(t.getType() + ",");
            }
            if (builder.length() > 0)
                builder.deleteCharAt(builder.length() - 1);
            methodList.add(arg0.getName().toString() + "(" + builder.toString()
                    + ")");
            startNumberList.add(startLine);
            endNumberList.add(endLine);
            // Voila!
            // System.out.println("Found method " + arg0.getName() +
            // " from line "
            // + startLine + " to line " + endLine + ".");

            return super.visitMethod(arg0, arg1);
        }
    }
}
